home *** CD-ROM | disk | FTP | other *** search
- Technion - Israel Institute of Technology
- Faculty of Electrical Engineering
- Laboratory of Computer Music
-
- MUZIKA - A Musical Notes and Scores Editor
- Internal Description of the Software
-
- Presented By: Lavy Libman & Yakov Aglamaz
- Conducted By: Noam Amir
-
- Contents:
- 1. Introduction
- 2. General description of the database
- 3. General description of the program modules
-
-
- 1. Introduction
-
- This part of the MUZIKA documentation intends to describe the internal
- structure of the software. We shall assume that the reader has learnt the
- User's Guide and knows how the editor looks and "feels", and how to operate
- the different features in it. We also assume the reader to be an experienced
- programmer in the C++ language under the Microsoft Windows environment
- (preferably using Borland C++ for Windows, as this is the compiler which was
- used to compile the software). No general C++ or Windows terms, unless
- related specifically to MUZIKA, will be explained herein.
-
- While reading the internal documentation, it should be remembered that the
- MUZIKA project is not a finished, commercial-style application. Indeed, from
- the very start the intention of the project was to write the minimum amount
- of code to implement the features required from a musical score editor,
- stressing the ease with which the code can be extended and reused.
-
- The program consists altogether of about 5400 lines of C++ code (not
- counting the source code of the class libraries provided by Borland), making
- an executable file of about 120K. We hope that this description of the
- internal structure of the code, as well as the extensive commenting in the
- code itself, will help the reader clarify how MUZIKA functions from the
- inside.
-
-
- 2. General description of the database
-
- Before we explain how the software functions from the inside, it is
- necessary to explain the structure of the database which keeps the melody in
- memory. In light of the database description, it will be easier to explain
- the tasks performed by the different functions in the software through the
- changes they make to the data in the database. We recommend the reader, as
- he goes through the explanations below, to browse at the MUZIKA.H file,
- which contains all kinds of global definitions relevant to different modules
- of the software, including the definitions of the classes that comprise the
- melody database.
-
- 2.1. The melody database
-
- At the very top, the melody has a few global parameters, followed by a list
- of parts. Each part has its information kept separately from other parts;
- the structure of a part will be explained later. This is implemented by
- having the Melody class - the topmost class in the database, of which there
- is precisely one instance at any given time - inherit its parameters from
- the MelodyParameters class, and add to that an IndexedList of parts. The
- global parameters of the melody include the staff width (in pixels); this is
- global to the entire melody, for this width has to be common to all staves
- in the melody if score displays are to be shown and printed. Other
- parameters that may be added here can be, for example, the name of the
- melody or the text color. On the other hand, the key signature and the time
- signature should not be kept as melody parameters, as these can change
- throughout the different sections of the music.
-
- The IndexedList class is an extension of the Array class from Borland's
- class library, designed specifically for the purpose of holding lists at the
- different levels of the MUZIKA database. It is essentially an Array, with
- the following functions added:
- - insertAt(Object &obj, int index) inserts an object at the given index in
- the list, pushing the other objects from this index one forward.
- - detachAt(int index) removes an object from the given index in the list,
- pulling objects from subsequent indexes to close the gap. The object itself
- is not destroyed.
- - destroyAt(int index) is similar to detachAt, except that the object is
- destroyed (i.e. its destructor is called).
- - printOn(ostream &out) writes the objects in the list to the stream by
- calling the printOn virtual function once for every object in the list.
-
- Continuing with the description of the MUZIKA database, the same idea of a
- few global parameters followed by a list is now repeated at the part level:
- the Part class inherits from the PartParameters class a few parameters
- global to the part, and follows with an IndexedList of staves. The
- parameters of a part in the current implementation are:
- - name is the part name.
- - multiplicity is the staff multiplicity, i.e. the number of single staves
- that are grouped together to create a multiple staff with common bars.
- - editYMin is the Y-coordinate of the topmost visible line of the part. This
- is not a constant parameter of the part, but rather used for displaying to
- remember which staves are visible at the moment. Keeping this parameter for
- all parts, instead of just for the currently visible one, ensures that when
- the display is flipped from one part to another (via the Layout/Page... menu
- selection), the staves initially visible in the new part will be those that
- were visible the last time the part was shown.
- Other parameters which a part may have can include, for example, the musical
- instrument that plays the part.
-
- Next, we come to how a staff is implemented. No surprises here; as the
- reader should have guessed by now, the staff is again no more than a few
- parameters followed by two lists this time: the list of point objects in the
- staff and the list of the continuous objects. These lists were separated to
- ease the treatment of the two object type groups, which is subtly different
- in most cases. The Staff class inherits its parameters from a
- StaffParameters class and has two IndexedList items corresponding to the two
- object lists. The staff parameters include, in the current implementation,
- only its coordinates: X, Y, and width. The Y coordinate of a staff is
- invariant to display scrolling; the actual Y coordinate of a staff on the
- screen is calculated as its Y coordinate minus the part's editYMin.
-
- The PointObject and ContinuousObject classes, both derived from
- MusicalObject, are the base classes defining the properties of a generic
- point
- object or continuous object, respectively. Every class defining a specific
- musical object type (such as Note, Pause, etc.) is derived from one of these
- two base classes. Actually, the MusicalObject class already defines the
- properties that every musical object must have, letting the PointObject and
- ContinuousObject classes only add the properties specific to the respective
- object type groups.
-
- An attribute that every musical object must have is the location of the
- object relative to the staff in which it is. The location attribute can take
- one of the following values (all defined in MUZIKA.H):
- - INSTAFF means that the object can be anywhere in the staff. Therefore, at
- the time of creation, the constructor of the object receives both the X and
- Y coordinates of the mouse cursor relative to the staff.
- - ABOVESTAFF means that the object is always located above the staff.
- Therefore, at the time of creation, the constructor of the object receives
- only the X coordinate of the mouse cursor.
- - BELOWSTAFF means that the object is always located below the staff.
- Objects having this attribute value are treated similarly to those which are
- ABOVESTAFF.
- - ABOVEMULTIPLE means that the object is always located above the multiple
- staff. Therefore, at the time of creation, no matter where the mouse was
- clicked, the object is inserted in the appropriate list of the topmost staff
- of the multiple staff.
- - BELOWMULTIPLE means that the object is always located below the multiple
- staff. Therefore, at the time of creation, no matter where the mouse was
- clicked, the object is inserted in the appropriate list of the bottommost
- staff of the multiple staff.
- - COMMONMULTIPLE means that a copy of the object will always be in every
- staff in the multiple staff. Therefore, at the time of creation, no matter
- where the mouse was clicked, a copy of the object is inserted in the list of
- every staff comprising the multiple staff.
- In addition, the value of ONEPERSTAFF can be added to any of the values
- above to indicate that only one such object can exist in a staff. Thus, for
- example, a Key object has a location attribute of INSTAFF+ONEPERSTAFF,
- meaning that there can be only one key in a staff, and the thick bar limits
- at the start or end of a staff have a location attribute of
- COMMONMULTIPLE+ONEPERSTAFF, meaning that there can only be one such bar
- limit per staff, and in that case there is a copy of it in every staff
- comprising the multiple staff.
-
- The MusicalObject class also defines a few member functions that every
- musical object should have. These are:
- - Draw(HDC hDC) draws the graphic image of the object in the given display
- context. This function is used to display the object.
- - printOn(ostream &out) saves the object data in the given output stream.
- This function is used when the melody is saved in a file.
- - clipOn(void far &*clipboard) saves the object data in the given memory
- location. This function is used during clipping (i.e. cutting or copying).
-
- The PointObject class, derived from MusicalObject, adds the properties that
- every point object must have. These include the X coordinate of the object,
- and the following functions:
- - Format(int &X) sets the object's X coordinate to the given value. This
- function is used when the part is reformatted. If a point object has any
- special actions required when its coordinate is changed, they can be added
- by having the specific class define its own version of this virtual function.
- - Width() returns the object width in pixels. Usually, this is the general
- object width defined in the Layout/Page... dialog box and kept in the
- pixelsPerObject variable. This function, too, is used in the process of part
- reformatting.
- - MIDIPlay(ostream &out) writes the MIDI event which the object represents
- to a temporary file. This function is used during the first pass of the MIDI
- file creation.
- - Duration() returns the duration of the MIDI event which the object
- represents, in units of 148-th of a full note. This function, too, is used
- in the process of creating the temporary file during the MIDI first pass.
-
- The ContinuousObject class, derived from MusicalObject, adds the properties
- that every continuous object must have. These include the Xleft and Xright
- coordinates of the object, and the following functions:
- - FormatLeft(int &Xleft) sets the object's Xleft coordinate to the given
- value. This function is used when the part is reformatted. If a continuous
- object has any special actions required when its left coordinate is changed,
- they can be added by having the specific class define its own version of
- this virtual function.
- - FormatRight(int &Xright) is similar to FormatLeft, but alters the Xright
- coordinate of the object.
-
- Summarizing what has been said about the database so far, we see that it has
- a structure of a 4-level tree, at the root of which sits the melody, on the
- second level - the parts, on the third level - the staves, and finally, on
- the leaves - the musical objects themselves. The object-oriented design of
- the database is readily seen. It should be emphasized that none of the
- functions, anywhere in the software, does not - in fact, cannot - make any
- assumptions about the nature of the objects at the leaves, e.g. test the
- type of a specific object and act differently if it is a loudness sign than
- if it is a note sign. Whatever actions are specific to the musical object
- classes are made in virtual member functions of the classes themselves.
-
- Having said this much, let us turn and see what specific musical object
- types have been defined in the current implementation of MUZIKA, remembering
- that this is only a minimal set, intended to demonstrate the tremendous
- possibilities of the design. No doubt does this set have to be extended to
- include many more class types for the application to become a really useful
- tool for editing high-featured scores. Anyway, the classes that have been
- implemented are:
- - Note is the class of note objects. The different note durations (full,
- half, etc.) are distinguished by the value of the class's duration
- attribute. In addition, the class defines an Y attribute representing the
- note height, and eventually its frequency.
- - Pause is the class of pause objects. The different pause durations are
- distinguished by the value of the class's duration attribute.
- - Key is the class of key objects. The different key types are distinguished
- by the value of the class's type attribute. An instance of this class always
- has a location value of INSTAFF+ONEPERSTAFF.
- - Beat is the class of time signatures (i.e. beats per bar). The different
- time signatures are distinguished by the value of the class's type attribute.
- - Bar is the class of bar limits. The different bar limit types (single,
- double, etc.) are distinguished by the value of the class's type attribute.
- An instance of this class always has a location value of COMMONMULTIPLE,
- with or without the addition of ONEPERSTAFF.
- - Loudness is the class of loudness point objects (forte, fortissimo, piano
- and pianissimo). The different loudness signs are distinguished by the
- class's loudness attribute.
- - Crescendo is the class incorporating the crescendo and diminuendo signs.
- The two signs are distinguished by the class's direction attribute.
- - Text is the class of text signs and instructions above or below the
- staves. The text itself (up to 16 characters) is kept in text.
-
- This concludes the description of the part of the database classes which is
- defined in the MUZIKA.H and OBJECTS.H header file. To get the impression of
- how things operate at the lowest levels of the database, the reader is
- invited to catch a glimpse of the member function definitions in
- OBJCLASS.CPP.
-
- 2.2. The symbols database
-
- The second large part of the database is the database of the symbols. After
- all, the user does not have a direct access to the melody database; rather,
- he creates the musical objects using symbols on the left of the screen.
-
- The symbols database is conceptually simpler than the melody database. Every
- symbol has its own class, from which there is exactly one instance. All the
- symbol classes are derived from the generic SymbolClass base class, which
- defines the properties that every symbol must have. The symbol class
- instances are held in a constant array called symbolArray.
-
- The following properties are defined in SymbolClass:
- - symbolID is a unique number that identifies the symbol. The ID is not just
- a random unique number; rather, it represents the symbol set where it
- belongs and its number within the set. In more detail, the formula to which
- a symbolID value must conform is:
- symbolID = 16 * (symbol set) + (symbol offset)
- where symbol set is the symbol set number (e.g. 1 for Notes, 2 for Keys,
- etc.), and symbol offset is the offset of the symbol, counted from 0 upwards
- (e.g. 0 for the topmost leftmost symbol, 1 for the topmost rightmost, etc.).
- - symbolType is a slightly misleading name for the attribute: it is actually
- the type of the object (either POINTOBJECT or CONTINUOUSOBJECT) that is
- created by the symbol.
- - BitmapName(LPSTR, int) returns the name of the symbol bitmap in the
- resource file, without the "B_" prefix. As can be easily verified by
- browsing in the resource file (MUZIKA.RES) using any resource editor, all
- the bitmaps are named "B_xxxx".
- - DrawSymbol(HDC, HDC, BOOL) draws the symbol in the given display context
- at the coordinates that are calculated from its own symbolID. This function
- uses the previous one to load the symbol bitmap from the resource file. The
- bitmap is drawn in reverse video if the symbol is active.
- - CreateObject(int, int, int) creates the object corresponding to the
- symbol. Sometimes, an single object type (such as Note) is created by
- several symbols; in that case, it is the duty of CreateObject to construct
- the appropriate version of the object and load its attribute variables with
- the proper values.
-
- It is pointless to give here a list of all the symbol classes derived from
- SymbolClass. Far better is to direct the reader to the SYMCLASS.CPP file,
- which is actually a library of the derived symbol classes and their versions
- of the three virtual functions defined in SymbolClass, above. As with the
- library of musical classes, we would like to emphasize how straightforward
- it is to add a new symbol to the library: just define the new class, derived
- from SymbolClass, and write its own versions for the three virtual functions
- BitmapName, DrawSymbol, and most important, CreateObject, all after having
- designed the symbol bitmap in the resource file (MUZIKA.RES) - and that is
- all there is to it.
-
-
- 3. General description of the program modules
-
- The source code of MUZIKA consists of over 5400 lines of C++ lines, not
- including the sources of Borland's class library. Naturally, a project of
- this size could not have been maintained without some sort of dividing the
- code into smaller modules. In fact, MUZIKA contains 16 *.CPP files,
- corresponding to logical modules of the software, containing functions that
- take care of different tasks. In addition to that, we should mention the *.H
- header files, especially MUZIKA.H and OBJECTS.H, that although do not
- contain any actual statements, they provide the definitions of classes,
- global variables, and function prototypes to be used by all the modules.
-
- In the sequel, we attempt to describe the operation of each module
- independently from the others, as much as this proves possible. When it was
- not possible, we ordered the module descriptions in such a way that there
- would only be references to modules described previously.
-
- In our descriptions, we do not provide an exact list of the functions, their
- parameters and return values. We feel that that would be a rather useless
- duplication of the source code itself, which is well and extensively
- documented as it is. Instead, we concentrate on the duty that the module
- fulfills within the overall design, and explain the implemented algorithms
- on the conceptual level. Either way, we recommend the reader to browse
- through the enclosed source code listings as he reads the description; this
- will without doubt help in understanding the logic behind the software
- design.
-
- 3.1. The main module (MAIN.CPP)
-
- The main module contains several initialization functions and the main
- window message-processing and painting functions. The entry point to the
- program is at the beginning of this module, in function WinMain, which does
- no more than calling the initialization functions and establishing a
- message-dispatching loop, as is customary in every Windows application. The
- initialization functions register the main window class, create and display
- the main and edit windows for the first time. The initialization code is
- entirely straightforward and is not much different from what can be found in
- any other Windows application.
-
- The main window function processes messages originated by Windows that are
- intended for the main window. For example, a WM_COMMAND message, indicating
- that the user has triggered an action using the menu, causes it to call the
- ProcessMenu function in the menu processing module. A WM_LBUTTONDOWN
- message, sent when the left mouse button is clicked, causes a call to the
- symbol identification function in the symbols module, to check if the cursor
- has been clicked on a symbol and make it active if so. Finally, a WM_PAINT
- message, sent when the main window needs repainting (i.e. when it has been
- resized, or another window has been removed from over it), triggers a call
- to the PaintEditWindow function, described next.
-
- The PaintEditWindow function is in charge of painting the edit window. It
- draws the lines separating between the symbols, and then calls the
- symbol-drawing functions in the symbols module.
-
- To summarize, then, the main module play a role of a dispatcher; any
- messages intended for the main window are, this is true, recognized here,
- but the actions taken in response are actually a part of other modules
- (specifically the menu processing module and the symbols module).
-
- 3.2. The IndexedList class module (INDEXED.CPP)
-
- The IndexedList module is not as important by itself as it is for other
- modules, that use the functions defined here constantly. In a previous
- chapter we have already explained the use of the IndexedList class, which is
- an extension of the Array class from Borland's class library, in the melody
- database. The functions that actually extend IndexedList over Array are
- defined here. These are the insertAt, detachAt, destroyAt, and printOn
- functions, all except the last similar to those of Array but keep the list
- elements at contiguous indexes at all times (as opposed to an Array, which
- allows for empty slots in the list).
-
- 3.3. The database module (DATABASE.CPP)
-
- The name of the database module may be slightly misleading: functions that
- make changes in the database are scattered throughout all parts of the
- software, without any central database management unit that would take care
- of keeping the database intact. What there actually is in the database
- module are member functions of classes that comprise the database. Among the
- functions defined here for all the class levels (Melody, Part, and Staff)
- are LoadFrom, which loads the appropriate class instance data from a file,
- and printOn, which does just the opposite - saves the class instance data in
- a file. There are also a few class constructor and destructor functions.
- Finally, the only Melody class instance is declared at the file end. The
- code in this module is straightforward and contains no subtleties; we feel
- therefore confident that the commenting of the code in the source file
- itself provides a sufficient explanation.
-
- 3.4. The display module (DISPLAY.CPP)
-
- The display module is responsible for displaying the information stored in
- the melody database in either format supported by MUZIKA (single part or
- score, in the current implementation). There are two important functions in
- the module, namely EditWindowProc, the edit window function, and
- PaintEditWindow, which is responsible for displaying staves and objects in
- the edit window. The other two functions, used only during initialization,
- are less important. By the way, the edit window is a child of the main
- window, containing the main window client area minus the area that is
- occupied by the symbols and status line. The edit window is automatically
- resized every time the main window changes its size. Staves and musical
- signs are drawn only in the edit window.
-
- The EditWindowProc function responds to all messages intended for the edit
- window, including cursor movement, mouse button clicks and double-clicks,
- and scrollbar changes. Covering this quite complex function's tasks is
- impossible without elaborating on the way it responds to each and every
- message type, so let's do just that.
-
- A WM_PAINT message, requesting a repaint of the edit window, is treated by
- the PaintEditWindow function, described later in this section. A
- WM_MOUSEMOVE message does not offer any cause for alarm, either - all that
- has to be done is change the cursor shape according to the current edit mode
- symbol (pencil, eraser or hand).
-
- The problems begin with the WM_LBUTTONDOWN message, because a simple
- clicking of the left button can trigger a variety of actions, according to
- what symbol is active at that moment. In any case, the actions which
- eventually lead to making the required changes in the database are done in
- the edit module (EDIT.CPP), but it is the duty of EditWindowProc to select
- the operation to be triggered. First of all, EditWindowProc checks that a
- single part is displayed (no editing is allowed on a score display). Then,
- it checks what the current symbol is.
- - If it is the pencil, a staff creating function in the edit module is
- called.
- - If it is the eraser, an object deleting function in the edit module is
- called.
- - If it is a symbol whose symbolType is POINTOBJECT, a point object
- inserting function in the edit module is called.
- In the remaining cases (the active symbol is the hand or corresponds to a
- CONTINUOUSOBJECT), no operation can be triggered just yet, because we need
- the point where the mouse button is released as well as where it is clicked.
- In that case, the mouse is captured to the edit window and the capture mode
- variable is set accordingly, to let the required operation to be completed
- after the mouse button is released.
-
- A WM_LBUTTONDBLCLK is processed similarly: if the current symbol is the
- eraser, the staff erasing function is called immediately, while if the
- current symbol is the hand, the mouse is captured until the time it is
- released, and only then will the staff movement operation be completed.
-
- In light of the above, a mouse button release notification message cannot be
- just thrown away: an operation may need to be completed at this stage.
- Therefore, in response to a WM_LBUTTONUP message, EditWindowProc checks
- whether the mouse has been captured to the edit window, indicating that
- there is indeed an unfinished operation pending for the mouse button release
- point. The nature of the unfinished operation is kept in the capture
- variable. Whatever the operation is (inserting a new continuous object or
- moving an object or a staff), the appropriate function in the edit module is
- called with the mouse button clicking and releasing points.
-
- There is yet another message which EditWindowProc has to process, and that
- is WM_VSCROLL, indicating an operation of some kind on the vertical scroll
- bar (line up, line down, page up, page down, or direct thumb movement). In
- response to the message, the thumb position is set to its new location, and
- the screen is refreshed. By the way, the scroll bar range is identical at
- all times to the range of Y coordinates of the staves in the current part
- (that is, the scroll bar values range from 0 to the Y coordinate of the last
- staff).
-
- To summarize, EditWindowProc takes much of the sting out of the editing
- process: it makes the relatively difficult decision of which operation is to
- be performed, leaving the edit module functions only to update the database
- given all the needed coordinates.
-
- The other nontrivial function in the display module, as we have noted, is
- PaintEditWindow, whose job is to display a part of the melody database on
- the screen. The display algorithm is similar whether it is a single-part
- display or a score display. In both cases, the relevant staves are drawn
- (using the Staff class Draw function, also included in this module at the
- end of the file), and the musical signs are drawn on those staves that
- reported that they were not entirely clipped, by going through the point and
- continuous object lists, calling the Draw virtual function for every item in
- the lists. The difference is only which staves are drawn; obviously, when
- displaying a score the parallel staves of all parts take part in the
- algorithm, while when displaying a single part only that part's staves are
- included. Anyway, after having drawn the staves, the status line in the main
- window is updated with information about which staves are visible. Strictly
- speaking, having the main window updated in one of its children's window
- function is a violation of the Windows "correct" programming style; what
- should have been done instead is define the status line area as another
- child window and have the edit window send an update message to that child
- whenever it needed an update, but the implemented solution was chosen for
- the sake of saving space (and even more modules).
-
- 3.5. The edit module (EDIT.CPP)
-
- The edit module functions are responsible for updating the melody database
- in response to a edit operation initiated by the user. Except for the first
- couple, all the functions are called from the display module, after the edit
- window function has selected the operation and obtained the cursor screen
- coordinates for it. The operations supported here include inserting,
- deleting and moving staves and point and continuous objects. To understand
- how these operations are taken care of the reader should have a clear view
- of the melody database structure, for which he should refer to section 2.1.
-
- Basically, all the edit functions operate similarly: they scan an
- appropriate list (either a list of staves in a part or of objects in a
- staff) in an attempt to find the list index where the operation takes place,
- and then insert or delete at that index, according to the operation that
- needs to be done. There are, however, certain subtleties here and there,
- that need specific care to be taken (a value of COMMONMULTIPLE for an
- object's location attribute, for example). The code of the functions is well
- commented, but we still feel that a general explanation of each function's
- task here will be helpful.
-
- InsertEmptyStaff inserts a new empty staff in the specified staves list
- given the staff Y location and the part multiplicity. The list is scanned
- for a staff having a Y coordinate larger that the inserted staff's one, and
- the new staff is inserted at that index. All the subsequent staves get their
- Y coordinate moved down by a staff height. Therefore, the list is kept
- sorted by the Y locations in ascending order. Also, if there is a marked
- block past where the staff is inserted, the block marking variables are
- incremented too.
-
- NewMultipleStaff creates a new multiple staff, after the user has clicked
- the pencil-on-staff symbol. A number of single staves equal to the part's
- staff multiplicity is inserted in the part's staff list by calling
- InsertEmptyStaff that number of times. The scroll bar range is readjusted to
- the new Y coordinate range (recall that the scroll bar range, from Windows'
- point of view, is from 0 to the last staff's Y coordinate).
-
- IdentifyStaff finds the index of the staff to which the given Y coordinate
- (obtained from the current cursor position) is closest. It does this by
- scanning the list of staves, returning one which actually contains the
- point, if it exists. Failing that, it returns the staff from which the
- point's Y distance is minimal, but in any case not more than half a staff's
- total width, as defined in the page layout dialog box and kept in the
- pixelsPerStaff global variable.
-
- DeleteMultipleStaff deletes the multiple staff identified by a Y coordinate
- (obtained from the current cursor position). Having identified the staff
- using IdentifyStaff, the function simply removes all the staves in the same
- multiple staff from the list. If the multiple staff contains any objects,
- the user is requested to confirm the operation.
-
- NewPointObject is probably one of the most important functions of the
- software, though it is not conceptually difficult. First of all, it creates
- a point object corresponding to the active symbol at the given cursor
- position (using the current symbol's CreateObject function - there is no
- idea about what the object is). Next, it checks the just created object's
- location attribute to decide where to insert it. If the object has a
- location value of INSTAFF, ABOVESTAFF, or BELOWSTAFF, it is simply inserted
- in the staff identified by IdentifyStaff. An object with a location value of
- ABOVEMULTIPLE or BELOWMULTIPLE is inserted in the top or bottom staff of the
- multiple staff, respectively. Finally, in case of a COMMONMULTIPLE object, a
- copy of it is inserted in every staff comprising the multiple staff. In
- addition to all of the above, if the object has the ONEPERSTAFF bit set, the
- staff is checked not to contain another object at the same place, reporting
- an error if this condition is not met.
-
- NewContinuousObject is mostly similar to NewPointObject, just described. It
- creates a continuous object corresponding to the active symbol at the given
- cursor coordinate), and inserts it in one of the staves from which the
- multiple staff is constructed, according to the object location attribute.
-
- DeleteMusicalObject may look frightening at first sight, considering the
- amount of code in it, but this actually follows from the majority of special
- cases that need to be taken care of. Basically, it scans the point and
- continuous object lists of the staff identified by IdentifyStaff, deleting
- the objects that are within (pixelsPerObject/2) pixels away from the given
- cursor position. If any of the deleted objects is COMMONMULTIPLE, the other
- staves in the same multiple staff are also scanned and this object is
- deleted from them too.
-
- MoveStaff moves a multiple staff from one place to another in the same part.
- Before actually moving, the destination is checked to be free from other
- staves. In the process of moving (achieved by detaching each staff of the
- multiple staff from the staff list and reinserting it at its new index), any
- staves that the moving has crossed (and are now on a different side relative
- to the moved staff) get their Y coordinate updated, and so is done to the
- block marking variables in case there was a marked block in the area.
-
- MoveMusicalObject moves a musical object by using the CutBlock and
- PasteBlock functions in BLOCK.CPP. Therefore, to understand the object
- moving operation, the user is referred to the description of the block
- operations module later in this chapter.
-
- 3.6. The menu processing module (MENU.CPP)
-
- The job of this module is to dispatch various requests made by the user
- through the main menu to the modules that take care of them. The ProcessMenu
- function is thus not more than one big switch statement, interpreting the
- menu item codes and either calling the appropriate functions in other
- modules directly or by creating a dialog box function instance first.
-
- Another function in this module is InitializeMenu, used during the software
- initialization. According to the Boolean parameter it receives, it enables
- or grays out the menu items that cannot be used before a melody is loaded in.
- 3.7. The file operations module (FILE.CPP)
-
- The file operations module is an interface to the functions offered to the
- user by the File menu, namely creating a new melody, loading and saving a
- melody, and converting a melody to a MIDI file. The functions themselves are
- not implemented in this module; what is rather contains is the functions of
- the dialog boxes created when the user selects different file operations.
-
- The first function in the source file is AskSave, which does the job of
- confirming any operation that may destroy the current melody (such as
- loading a new one or quitting the application) when the current melody has
- been modified since the last time is was saved. The information about
- whether the melody has been modified is kept in the melodyModified global
- variable, which is set to TRUE by all the editing functions that modify the
- melody, and to FALSE by the saving function.
-
- The rest of the module functions are straightforward dialog box functions,
- whose duty is to process typical dialog box messages, such as WM_INITDIALOG
- to initialize a dialog box or WM_COMMAND to respond to an action made by the
- user. Actually, they differ little except for what they do after the user
- selects OK to confirm his selections:
- - DialogNew empties the current melody from any parts it might have
- contained, and creates a new melody with a single part named UNNAMED, with a
- staff multiplicity of 1. It also puts default values into a few global
- variables, such as melodyModified and scoreDisplay.
- - DialogOpen opens the file whose name the user has given (reporting an
- error if that file does not exist) and issues a call to the LoadFrom
- function of the Melody class to load the melody.
- - DialogSaveAs opens the file whose name the user has given (issuing a
- warning if that file already exists) and calls the printOn function of the
- Melody class to save the melody.
- - DialogCreateMIDI calls the MIDI file creation module to convert the
- current melody to a MIDI file.
-
- 3.8. The printing module (PRINT.CPP)
-
- The printing module contains the functions that make a hard copy of a melody
- or of one of its parts on a printer. In any case, the printer selected as
- the Windows default is used. There are two functions, PrintSinglePart and
- PrintScore, that take care of the process of printing a single part or the
- score of all parts. The interface function to the module is PrintEditWindow,
- which creates a printer device context and calls one of the other two
- printing functions according to what the current display setting.
-
- Both printing functions operate quite similarly, the main difference being
- the source of their staves, taken either from only one part or all parts,
- respectively. Both use the staves' and objects' Draw functions to create a
- multiple staff image in the printer device context and then flush it to the
- printer. The multiple staves are therefore printed one by one.
-
- 3.9. The layout module (LAYOUT.CPP)
-
- The layout module implements the layout dialog box functions. Specifically,
- it contains three functions:
- - DialogParts, the Layout/Parts... dialog box function;
- - DialogPage, the Layout/Page... dialog box function;
- - DialogNewPart, the Layout/Parts.../New... dialog box function.
-
- The dialog box functions code is tailored to the specific form of the dialog
- boxes themselves, designed in the MUZIKA.RES resource file under the names
- D_PARTS, D_PAGE and D_NEWPART respectively. To understand the code, the
- reader must try using the MUZIKA editor for a while and get the feel of how
- the mentioned dialog boxes look, but once that stage is over, the code is as
- straightforward as any dialog box function code usually is. All the three
- functions respond to messages intended for their dialog boxes, such as
- WM_INITDIALOG to initialize a dialog box (which means, for the mentioned
- dialog boxes, to fill the list boxes with the lists of part names), and
- WM_COMMAND to respond to an action made by the user. After the user selects
- OK to confirm his selections, these functions complete their duties by
- copying these selections to global variables, such as pixelsPerStaff for the
- staff height and pixelsPerObject for the object width, both selected in the
- Page dialog box. It is pointless to elaborate on any of these functions
- further, as they are commented well enough for the reader to be able to
- understand them easily by himself.
-
- 3.10. The About dialog box module (ABOUT.CPP)
-
- The About dialog box module is the smallest module of the software. As its
- name suggests, it has no more than the function for the dialog box that
- appears upon the user's selection of Help/About... The trivial function code
- should pose no problem for the reader who has had the experience with the
- File and Layout dialog box much more complicated functions.
-
- 3.11. The reformatting module (FORMAT.CPP)
-
- The reformatting module implements a part reformatting algorithm. In its
- current version, the algorithm reformats each multiple staff by itself; bars
- and musical objects do not move from one staff to another. Let the reader,
- however, have no illusion; even the implemented algorithm is complicated
- enough as it is, which is easily proved at least by the length of the
- module. The central function of the module is FormatMultipleStaff, which
- adjusts the X coordinates of musical objects on one multiple staff. The
- interface function of the module is FormatEntirePart, that does no more than
- calling FormatMultipleStaff once per every multiple staff contained in the
- part.
-
- Before describing the algorithm itself, a word should be said about the data
- structures it uses. At the beginning of FormatMultipleStaff a few lists are
- allocated on the heap for later use. The lists are as long as there are
- single staves in a multiple staff, i.e. every list item holds a datum about
- the single staff corresponding to it. These lists are:
- - duration holds the durations left (in units of 148-th of a full note) for
- the current object on the staves. Whenever this duration is over, the next
- object should be brought in on the corresponding staff.
- - index holds the indexes of the current objects for all staves.
- - atCommon holds TRUE for a staff who has stopped on a COMMONMULTIPLE
- object. It is used to ensure that such objects remain at the same X
- coordinate on all staves after the reformatting process.
- - staffEnd holds TRUE for a staff that has no objects left to reformat.
-
- The next few lines create two lists combining the continuous object lists of
- all the single staves, one sorted by Xleft and one sorted by Xright. This
- operation is required because later, when the X coordinates are changed
- considering only the point objects, the continuous objects' left and right
- coordinates will not be changed simultaneously. Note that during this stage,
- the continuous objects appear on three lists at once! However, this does not
- contradict in any way the normal usage of the IndexedList class, holding
- only pointers to the actual objects instead of copies of the objects
- themselves.
-
- Next comes the main loop of the algorithm, which lasts as long as the
- multiple staff has not been completely reformatted. At every stage of the
- loop the minimum value in the duration list is subtracted from all the
- others. In those staves at which the duration reaches zero as a result of
- the subtraction, the next object is placed at the current X position,
- denoted by the currX variable. If several objects were at the same place
- originally (for example, an accord), they are all moved to their new place
- together. This includes the continuous objects having one of the coordinates
- at the current X position as well as the point objects. To conclude the
- loop, the index list of indexes is updated and the current X position is
- advanced by the default object width, held in pixelsPerObject.
-
- If the current object in any staff is a COMMONMULTIPLE object, the atCommon
- flag is marked for that staff. Later, no objects will be reformatted further
- in that staff before atCommon is raised to TRUE at all staves. This ensures
- that COMMONMULTIPLE objects remain at a common X coordinate (though possibly
- different from the original). As a consequence, objects cannot cross bar
- limits; whatever is in a bar before reformatting stays there afterwards,
- even if there is a logical error and the object durations do not sum up to
- the same value.
- 3.12. The MIDI file creation module (MIDI.CPP)
-
- The MIDI file creation functions are gathered in this module. They are
- activated when the user chooses Create MIDI... from the File menu. The
- length of the file witnesses that the problem of creating a MIDI file is far
- from being simple. We hope to clarify our approach to the solution in the
- sequel.
-
- The conversion of a melody to a MIDI file is performed in two stages, or
- passes, as they are called in the program text. During the first pass, a
- temporary file is created with information about the MIDI events represented
- by the objects in the melody. The second pass adds information that was not
- be available during the first translation pass, such as track lengths, and
- creates a final MIDI file.
-
- The first pass is implemented by the MIDIFirstPass function, which simply
- calls MIDIStaff once per every multiple staff in the score. The algorithm
- implemented in MIDIStaff bears some resemblance to that of reformatting a
- multiple staff (see function FormatMultipleStaff in the reformatting module
- description). In particular, there are the duration and index arrays having
- the same purpose.
-
- The main loop of the first-pass translation algorithm lasts as long as the
- multiple staff has not been completely reformatted. At every stage of the
- loop the minimum value in the duration list is subtracted from all the
- others. In those staves at which the duration reaches zero as a result of
- the subtraction, the next object's MIDIPlay virtual function is called to
- put a description of the MIDI event represented by that object in the
- temporary file. If several objects were at the same place originally (for
- example, an accord), they are all MIDIPlayed together. To conclude the loop,
- the index list of indexes is updated, advancing the current object in the
- staves that took part in the play.
-
- Before we can explain the second pass of the algorithm, it is necessary to
- elaborate on the format of the temporary file available after the first
- pass. Ignoring for a while the meta-events defined by objects other than
- notes and pauses, the temporary file contains note-on events written by the
- Note objects and note-off events written by the Pause objects. The events
- are simply there in the file one after another, each event keeping such
- information as part number, note frequency, and most important - the
- duration. However, the MIDI file format does not use durations of events but
- rather delta times between events. This fact creates a translation problem
- from the temporary file format to the final MIDI file format, because each
- note event having any duration is translated to two MIDI events: a note-on
- at the beginning and a note-off at the end of its duration. This problem is
- solved in the second translation pass.
-
- The second-pass translation could be achieved by building a list of events
- from the temporary file, adding two events to the list for every note and
- keeping the list ordered by the time of the note appearances. This is
- precisely how MIDISecondPass works, except for two differences: one is that
- there are several such lists, one of each part (kept in the event[] array),
- and the other is that it does not build the list in its entirety before
- writing it to the MIDI file. Instead, as soon as it becomes clear that a
- specific event will not be preceded by another, that event is written to the
- MIDI file. Otherwise there would have been a danger of getting out of memory
- very quickly during the translation process.
-
- The class that is instantiated in the list(s) described above is MIDIEvent.
- To what every list item (of class Object) has, it adds the properties of
- event code, frequency, and delta time. In the algorithm implemented in
- MIDISecondPass, the lists of MIDIEvent instances are kept as delta lists, in
- which the delta time of an event is its actual delta time from the previous
- event on the list. Such arrangement eases the insertion and deletion of
- events from the lists.
-
- In the main loop of MIDISecondPass, events are read from the temporary file
- and inserted in the part lists as described above until no lists remain
- empty. At this point, it is clear that one of the events already in the
- lists must come before any subsequent ones. Among the first events in the
- lists, the one with the minimum delta time is chosen for writing into the
- MIDI file. After it is written and removed from the list, the temporary file
- is again read until no list is empty, etc. The process is finished at the
- end of the temporary file, after which the events already in the lists are
- flushed in an appropriate order to the MIDI file.
- Of course, the general structure of a MIDI file (its division to a header
- and playing tracks) is preserved in the MIDISecondPass function that creates
- the final-version MIDI file.
-
- 3.13. The block operations module (BLOCK.CPP)
-
- The block operations module defines four functions, corresponding to the
- four operations that can be performed on blocks: marking, copying, cutting
- and pasting. The functions are executed from the block operation symbol
- class instances when the user clicks the mouse with any of these symbols
- active.
-
- The block marking function (MarkBlock) code is trivial: it only has to
- assign the block marking variables, which are markBeginStaff, markBeginX,
- markEndStaff and markEndX, with the proper values. These variables being
- global, the display module's PaintWindowProc function can figure out where
- to display the musical text in reverse video. Also, any editing operation
- that may alter the block marking variables (such as inserting a new staff)
- must be sure to update their values whenever a possibility of that happening
- exists.
-
- The other functions, however, are not that simple. The operation of CutBlock
- and CopyBlock is extremely similar, the only difference being that CutBlock
- destroys the objects it clips, whereas CopyBlock doesn't. Both allocate a
- memory block in which the clipped objects' information will be written and
- that will be attached to the clipboard later on. Then they loop on the
- staves' point and continuous object lists, finding those objects contained
- in the marked block, and call their clipOn virtual functions, that write the
- objects' information in the memory block. CutBlock also removes the clipped
- objects from the lists and destroys them. Finally, the memory block handle
- is attached to the Windows clipboard. From now on, the information in the
- clipboard is independent of the current MUZIKA application instance, and it
- can be pasted in any other MUZIKA window.
-
- PasteBlock does the reverse operation of restoring musical objects out of
- the information available in a clipboard handle. During pasting, a block
- that is entirely contained in one multiple staff is treated differently from
- a block scattered among several multiple staves. The difference is expressed
- by the fact that a one-staff block can be pasted at any location on an
- existing staff, enabling objects to be moved horizontally as well, whereas
- pasting a many-staves block recreates the staves with the original X
- coordinates of the objects.
-
- The pasting process is performed by reading the object types one by one from
- the clipboard handle and reinstantiating them at their new places. Actually,
- PasteBlock distinguishes only between a staff object and whatever is not a
- staff (i.e. is a MusicalObject). Further separation to the different musical
- class types is done by the PasteObject function in the musical class library
- contained in OBJCLASS.CPP.
-
- 3.14. The symbol operations module
-
- The symbol operations module defines the functions that are common to all
- symbols. This is the module that works extra hours when the user chooses a
- symbol set from the Symbols submenu, or when he changes the active symbol
- within a given set. The functions presented by the module are summarized
- below. We recommend the reader to review the symbols database organization,
- described in section 2.2.
-
- DisplayEditModeSymbols displays the edit mode symbols on the screen top,
- marking the current by reverse video. In the symbols' module terminology,
- edit mode symbols are the three constantly appearing symbols at the screen
- top: the pencil, the eraser and the hand. The active edit mode symbol, if
- any, is marked by the variable activeEditMode. Whether an edit mode symbol
- is active at all (a regular symbol from a set on the screen left can be
- active instead) is marked in the variable editModeSymbolActive.
-
- IdentifyEditModeSymbol checks if the given cursor position is on one of the
- edit mode symbols. If so, it makes that symbol active and refreshes the
- display accordingly.
-
- RefreshSymbols displays the current symbol set on the screen left, marking
- the current symbol, if any, by reverse video. The active symbol is specified
- by the pair of variables activeSymbolSet and activeSymbol. In addition, a
- pointer to the current symbol class instance is held in the currentSymbol
- pointer.
-
- IdentifySymbol checks if the given cursor position is on one of the symbols
- on the screen left. If so, it makes that symbol active and refreshes the
- display accordingly, updating the currentSymbol pointer.
-
- Finally, GetActiveSymbol and GetCurrentSymbol return the current symbol, by
- ID or as a pointer to the symbol class instance, respectively.
-
- 3.15. The symbols class library (SYMCLASS.CPP)
-
- The symbols class library gathers the definitions of the symbol classes. As
- the reader will recall from section 2.2, for every symbol there is exactly
- one symbol class, derived from the generic base class SymbolClass, with one
- instance. In particular, a symbol class must redefine the virtual functions
- defined in SymbolClass, which are BitmapName, DrawSymbol, and CreateObject.
- Probably the most important of these is CreateObject, that creates a musical
- object corresponding to the symbol and returns a pointer to it. Only because
- it is defined as virtual for all the symbol classes does it become possible
- for the editing process, especially the NewPointObject and
- NewContinuousObject functions, to behave in a truly object-oriented manner,
- without knowing the types of the objects on which they operate at all.
-
- The symbol class instances - or more precisely, pointers to them - are kept
- in a list called symbolList, sorted by their ID code. For information about
- what a symbol ID is, refer to section 2.2. This list, declared at the end of
- SYMCLASS.CPP, is searched whenever the active symbol or symbol set changes.
- Except for that, there is little we can add to the above sentences. The
- reader is invited to browse the SYMCLASS.CPP to see the symbol classes
- define, in an entirely straightforward manner, their own versions of the
- virtual functions listed above.
- 3.16. The musical class library (OBJCLASS.CPP)
-
- The musical class library contains the definitions of the musical classes'
- versions of the pure virtual functions defined in MusicalClass (the base
- class for all musical object types). The different musical classes
- themselves are defined in the OBJECTS.H header file. More specifically, each
- musical class (of which there are currently seven: Note, Pause, Key, Beat,
- Bar, Loudness, Crescendo, and Text) has its own versions of the following
- functions:
- - Three different constructor functions: one that receives the instance data
- directly as parameters; one that gets as a parameter the input stream from
- which to read the instance data; and one that gets the instance data from a
- memory block attached to the clipboard.
- - A Draw function, that draws the object in the given display context, using
- (if needed) the display module's global variables staffX, staffY, and
- staffLoc. This functions is used to draw object images during displaying and
- printing.
- - A printOn function, that writes the object data to the given output stream
- in a way that enables its complete reconstruction. This function is used
- when the melody is saved.
- - A clipOn function, that writes the object data to the given memory block,
- presumably attached to a clipboard handle. This function is used when the
- object is inside a clipped (i.e. cut or copied) block.
-
- In addition, point object classes are allowed to redefine the Format, Width,
- MIDIPlay, and Duration virtual functions, and continuous object classes can
- make their own versions of FormatLeft and FormatRight. This may be needed in
- cases where an object has actions to perform during formatting or
- translating a MIDI file other than the default ones, defined in MUZIKA.H.
-
-